package cn.buk.qms.service;

import cn.buk.common.JsonResult;
import cn.buk.common.sc.CommonSearchCriteria;
import cn.buk.common.util.DateUtil;
import cn.buk.common.util.DecimalUtil;
import cn.buk.common.util.FlightTicketUtil;
import cn.buk.common.util.StringUtil;
import cn.buk.qms.BillUtil;
import cn.buk.tms.dto.*;
import cn.buk.tms.entity.order.OrderNoInfo;
import cn.buk.tms.sc.SearchBillInfoCriteria;
import cn.buk.tms.sc.SearchFlightOrderCriteria;
import cn.buk.tms.sc.SearchSettlementCriteria;
import cn.buk.tms.sc.SearchTprCriteria;
import cn.buk.tms.common.dto.CustomerDto;
import cn.buk.tms.dao.*;
import cn.buk.tms.entity.base.Airport;
import cn.buk.tms.entity.bill.*;
import cn.buk.tms.entity.misc.*;
import cn.buk.tms.entity.order.vas.VasOrder;
import cn.buk.tms.entity.order.vas.VasOrderPassenger;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;

import static cn.buk.common.Constant.DATE_YYYY_MM_DD;
import static cn.buk.tms.entity.misc.TicketSale.*;
import static cn.buk.tms.entity.misc.TieInProduct.INSURANCE;
import static cn.buk.tms.common.constant.TmsConstant.FLIGHT_AUTO_TICKETING_BSP;
import static cn.buk.tms.common.constant.TmsConstant.FLIGHT_AUTO_TICKETING_GP;
import static cn.buk.tms.common.constant.TmsOrderConstant.*;
import static java.util.stream.Collectors.toList;

;

/**
 * 账单相关的操作： 查找，创建
 *
 * @author yfdai
 * @date 2017/7/17
 */
@Component
public class BillServiceImpl extends BaseAbstractService implements BillService {

  private static final Logger logger = LogManager.getLogger(BillServiceImpl.class);

  @Autowired
  private FlightCharterDao flightCharterDao;

  @Autowired
  private FlightPolicyDao flightPolicyDao;

  @Autowired
  private BillDao billDao;

  @Autowired
  private SettlementDao settlementDao;

  @Autowired
  private BillSplitDao billSplitDao;




  public static final  String ERROR_MESSAGE_CUSTOMER_NOT_EXISTED = "客户不存在";
  public static final  String ERROR_MESSAGE_SUPPLIER_NOT_EXISTED = "供应商不存在";
  public static final  String ERROR_MESSAGE_PAYMENT_METHOD_NOT_EXISTED = "支付方式不存在";
  public static final  String ERROR_MESSAGE_BILL_NOT_EXISTED = "账单不存在";

  public static final  String ERROR_MESSAGE_AMOUNT_LESS = "余额不足，无法销账";


  /**
   * 查找账单信息
   *
   * @param enterpriseId 用户
   * @param sc           查找条件
   * @return 符合条件的账单列表
   */
  @Override
  @Secured({RoleInfo.ROLE_SUPERVISOR, RoleInfo.ROLE_CASHIER, RoleInfo.ROLE_ADMIN})
  public List<BillInfo> searchMyBillInfo(int enterpriseId, SearchBillInfoCriteria sc,
                                         int customerId) {
    return this.searchMyBillInfo(enterpriseId, sc, customerId, false);
  }

  @Override
  public List<BillInfo> searchMyBillInfo(final int enterpriseId, SearchBillInfoCriteria sc,
                                         int customerId, final boolean getDetail) {
    //调整查询条件
    adjustSearchBillCriteria(enterpriseId, sc);

    boolean getBillDetail = getDetail;
    if (BILL_TYPE_FLIGHT.equalsIgnoreCase(sc.getBillType()) && StringUtils.isNotBlank(sc.getLikePsgName())) {
      //查找对应的FlightOrderNo(原订单号），然后通过原订单号查找相关联的所有订单号）
      List<String> flightOrderNos = billDao.searchBillFlightOrderNos(enterpriseId, sc.getLikePsgName());
      sc.setFlightOrderNos(flightOrderNos);
      sc.setBillType(null);

      getBillDetail = true;
    }

    sc.setCustomerId(customerId);

    //检查是否多项表达式
    if (sc.getOrderNo().length() > 0) {
      String[] orderNos = sc.getOrderNo().split("[ ,，;；]");
      for(String orderNo: orderNos) {
        if (orderNo.trim().length() > 0) {
          sc.getFlightOrderNos().add(orderNo.trim());
        }
      }

      if (!sc.getFlightOrderNos().isEmpty()) {
        sc.setOrderNo("");
      }
    }

    List<BillInfo> billInfos = billDao.searchMyBillInfo(enterpriseId, sc);

    if (getBillDetail) {
      doGetBillDetail(enterpriseId, billInfos);

      if (StringUtils.isNotBlank(sc.getLikePsgName())) {
        //只返回包含该乘机人姓名的订单列表
        billInfos = billInfos.stream().filter(t -> t.hasPsgName(sc.getLikePsgName())).collect(Collectors.toList());
      }
    }

    return billInfos;
  }

  private void adjustSearchBillCriteria(final int enterpriseId, SearchBillInfoCriteria sc) {
    if ("4".equalsIgnoreCase(sc.getTicketStatus())) {
      //退票，如果供应商还是BSP，则把GP的退票也考虑进去
      if (sc.getSupplierId() > 0) {
        Supplier supplier = enterpriseDao.getSupplierById(sc.getSupplierId());
        if (supplier != null && supplier.getIsBsp() == FLIGHT_AUTO_TICKETING_BSP) {
          sc.getSupplierIds().add(sc.getSupplierId()); //BSP供应商
          List<Supplier> list = enterpriseDao.searchSupplierByTicketingType(enterpriseId, FLIGHT_AUTO_TICKETING_GP);
          if (!list.isEmpty()) {
            sc.getSupplierIds().add(list.get(0).getId());
          }
        }
      }
    }
  }

  @Override
  public List<BillInfo> searchBillInfoByOrderNo(final int enterpriseId, final String orderNo) {
    SearchBillInfoCriteria sc = new SearchBillInfoCriteria();
    sc.setOrderNo(orderNo);

    return billDao.searchMyBillInfo(enterpriseId, sc);
  }

  /**
   * 获取所有账单的详情
   */
  private void doGetBillDetail(int enterpriseId, List<BillInfo> billInfos) {
    List<String> flightBillNos = new ArrayList<>();
    List<String> refundBillNos = new ArrayList<>();
    List<String> changeBillNos = new ArrayList<>();
    List<String> vasBillNos = new ArrayList<>();
    List<String> hotelBillNos = new ArrayList<>();
    List<String> trainBillNos = new ArrayList<>();
    List<String> carBillNos = new ArrayList<>();

    for (BillInfo info : billInfos) {
      switch (info.getBillType()) {
        case BILL_TYPE_FLIGHT:
          flightBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_FLIGHT_REFUND:
          refundBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_FLIGHT_CHANGE:
          changeBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_VAS:
          vasBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_HOTEL:
          hotelBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_TRAIN:
          trainBillNos.add(info.getBillNum());
          break;
        case BILL_TYPE_CAR:
          carBillNos.add(info.getBillNum());
          break;
        default:
          break;
      }
    }

    List<Airport> airports = basicDataDao.searchAirport(null);

    if (!flightBillNos.isEmpty()) {
      doGetFlightBillDetails(enterpriseId, billInfos, flightBillNos, airports);

    }

    if (!refundBillNos.isEmpty()) {
      doGetRefundBillDetail(enterpriseId, billInfos, refundBillNos, airports);
    }

    if (!changeBillNos.isEmpty()) {
      doGetChangeBillDetail(enterpriseId, billInfos, changeBillNos, airports);
    }

    if (!vasBillNos.isEmpty()) {
      doGetVasBillDetail(enterpriseId, billInfos, vasBillNos);
    }

    if (!hotelBillNos.isEmpty()) {
      doGetHotelBillDetail(enterpriseId, billInfos, hotelBillNos);
    }

    if (!trainBillNos.isEmpty()) {
      doGetTrainBillDetail(enterpriseId, billInfos, trainBillNos);
    }

    if (!carBillNos.isEmpty()) {
      doGetCarBillDetail(enterpriseId, billInfos, carBillNos);
    }
  }

  private void doGetFlightBillDetails(final int enterpriseId, List<BillInfo> billInfos, List<String> flightBillNos,
                                     List<Airport> airports) {
    for (String billNo : flightBillNos) {
      for (BillInfo billInfo : billInfos) {
        if (billNo.equalsIgnoreCase(billInfo.getBillNum())) {
          doGetFlightBillDetail(enterpriseId, billInfo, airports);
        }
      }
    }
  }

  private void doGetFlightBillDetail(final int enterpriseId, BillInfo billInfo, List<Airport> airports) {
    List<BillFlightTicket> tickets = billDao.searchBillFlightTickets(enterpriseId, billInfo.getBillNum());
    for (BillFlightTicket ticket : tickets) {
      billInfo.getTickets().add(ticket);

      BillUtil.processRouteDetail(ticket.getRouteDetail(), ticket.getFlights());
      processAirportName2(ticket.getFlights(), airports);

      if (ticket.getDetails().isEmpty() && !ticket.getFlights().isEmpty()) {
        //防止在账单数据导出时ticket.getDetails()为空导致数据导不出来
        for (FlightInfo fltInfo : ticket.getFlights()) {
          BillFlightTicketDetail detail = new BillFlightTicketDetail();

          BeanUtils.copyProperties(fltInfo, detail);
          detail.setDportName(fltInfo.getDepartureAirportName());
          detail.setAportName(fltInfo.getArrivalAirportName());
          detail.setFlight(fltInfo.getFlightNo());

          ticket.getDetails().add(detail);
        }
      }
    }
  }

  private void doGetRefundBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> refundBillNos,
                                     List<Airport> airports) {
    List<BillFlightRefundTicket> tickets = billDao
            .searchBillFlightRefundTickets(enterpriseId, refundBillNos);
    for (BillFlightRefundTicket ticket : tickets) {
      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(ticket.getBillNum())) {
          info.getRefundTickets().add(ticket);
          BillUtil.processRouteDetail(ticket.getRemark(), ticket.getFlights());
          processAirportName2(ticket.getFlights(), airports);
          break;
        }
      }
    }
  }

  private void doGetChangeBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> changeBillNos,
                                     List<Airport> airports) {
    List<BillChangeOrder> tickets = billDao.searchBillChangeOrders(enterpriseId, changeBillNos);
    for (BillChangeOrder ticket : tickets) {
      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(ticket.getBillNum())) {
          info.getChangeOrders().add(ticket);
          BillUtil.processRouteDetail(ticket.getRemark(), ticket.getFlights());
          processAirportName2(ticket.getFlights(), airports);
          break;
        }
      }
    }
  }

  private void doGetVasBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> vasBillNos) {
    List<BillVasOrder> tickets = billDao.searchBillVasOrders(enterpriseId, vasBillNos);
    for (BillVasOrder ticket : tickets) {
      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(ticket.getBillNum())) {
          info.getVasOrders().add(ticket);
          break;
        }
      }
    }
  }

  private void doGetHotelBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> hotelBillNos) {
    List<BillHotelBooking> tickets = billDao.searchBillHotelBookings(enterpriseId, hotelBillNos);
    for (BillHotelBooking ticket : tickets) {
      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(ticket.getBillNum())) {
          info.getHotelBookings().add(ticket);
          break;
        }
      }
    }
  }

  private void doGetTrainBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> trainBillNos) {
    List<BillTrainTicket> tickets = billDao.searchBillTrainTickets(enterpriseId, trainBillNos);
    for (BillTrainTicket ticket : tickets) {
      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(ticket.getBillNum())) {
          info.getTrainTickets().add(ticket);
          break;
        }
      }
    }
  }

  private void doGetCarBillDetail(final int enterpriseId, List<BillInfo> billInfos, List<String> carBillNos) {
    for (String billNum : carBillNos) {
      List<BillRentalCarOrder> tickets = billDao.searchBillCarOrders(enterpriseId, billNum);

      for (BillInfo info : billInfos) {
        if (info.getBillNum().equalsIgnoreCase(billNum)) {
          info.getCarOrders().addAll(tickets);
          break;
        }
      }
    }
  }

  /**
   * 查找单个订单的详情
   */
  @Override
  @Secured({RoleInfo.ROLE_ADMIN, RoleInfo.ROLE_CASHIER})
  public BillInfo searchBillInfoDetail(int enterpriseId, int id, String billNum) {
    BillInfo billInfo = id > 0 ? billDao.getBillInfo(id) : billDao.getBillInfo(enterpriseId, billNum);

    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return null;
    }

    switch (billInfo.getBillType()) {
      case BILL_TYPE_FLIGHT:
        billInfo.setTickets(billDao.searchBillFlightTickets(enterpriseId, billInfo.getBillNum()));
        break;
      case BILL_TYPE_FLIGHT_REFUND:
        List<BillFlightRefundTicket> refundTickets = billDao.searchBillFlightRefundTickets(enterpriseId, billInfo.getBillNum());
        for (BillFlightRefundTicket t : refundTickets) {
          BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
        }
        billInfo.setRefundTickets(refundTickets);
        break;
      case BILL_TYPE_FLIGHT_CHANGE:
        List<BillChangeOrder> orders = billDao.searchBillChangeOrders(enterpriseId, billInfo.getBillNum());
        for (BillChangeOrder t : orders) {
          BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
        }
        billInfo.setChangeOrders(orders);
        break;
      case BILL_TYPE_TRAIN:
        billInfo.setTrainTickets(billDao.searchBillTrainTickets(enterpriseId, billInfo.getBillNum()));
        break;
      case BILL_TYPE_HOTEL:
        billInfo.setHotelBookings(billDao.searchBillHotelBookings(enterpriseId, billInfo.getBillNum()));
        break;
      case BILL_TYPE_VAS:
        billInfo.setVasOrders(billDao.searchBillVasOrders(enterpriseId, billInfo.getBillNum()));
        break;
      case BILL_TYPE_CAR:
        billInfo.setCarOrders(billDao.searchBillCarOrders(enterpriseId, billInfo.getBillNum()));
        break;
      default:
        break;
    }

    if (StringUtils.isEmpty(billInfo.getSettlementNo())) {
      billInfo.setSettleDate(null);
    }

    return billInfo;
  }


  @Override
  public double searchBillSum(int enterpriseId, String billNum) {
    BillInfo billInfo = billDao.getBillInfo(enterpriseId, billNum);

    return billInfo == null ? 0 : billInfo.getBillSum();
  }

  @Override
  public void addBillFlightTickets(BillInfo info) {
    List<BillFlightTicket> tickets = billDao
            .searchBillFlightTickets(info.getEnterpriseId(), info.getBillNum());
    info.setTickets(tickets);
  }

  /**
   * 创建火车票销售订单
   *
   * @param enterpriseId         系统用户
   * @param operator             操作员的用户名
   * @param customerEnterpriseId 客户id
   */
  @Override
  public JsonResult createTrainTicket(final int enterpriseId, String operator,
                                      int customerEnterpriseId, int supplierId, int paymentId, BillTrainTicket ticket) {
    if (ticket.getPassengerName().length() == 0 ||
            ticket.getTrainNo().length() == 0 ||
            ticket.getDcity().length() == 0 ||
            ticket.getAcity().length() == 0 ||
            ticket.getDdate() == null ||
            ticket.getSeatClass().length() == 0) {
      return JsonResult.createJsonResult(-2, "火车票数据不能留空");
    }

    // 客户类型
    int customerType = customerEnterpriseId > 0 ? 1 : 0;

    Customer customer = customerDao.getCustomerById(customerEnterpriseId);
    if (customer == null || customer.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_CUSTOMER_NOT_EXISTED);
    }

    Supplier supplier = enterpriseDao.getSupplierById(supplierId);
    if (supplier == null || supplier.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-3, ERROR_MESSAGE_SUPPLIER_NOT_EXISTED);
    }

    PaymentMethod payment = enterpriseDao.getPaymentMethodById(paymentId);
    if (payment == null || payment.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-4, ERROR_MESSAGE_PAYMENT_METHOD_NOT_EXISTED);
    }

    final String billNum = orderNoGenerator.generateOrderNo(enterpriseId, OrderNoInfo.BILL_TRAIN);
    final Date opDate = DateUtil.getCurDate();

    ticket.setEnterpriseId(enterpriseId);
    ticket.setCustomer(customer);
    ticket.setSaleDate(opDate);
    ticket.setPayMode(PAY_TYPE_BILL + "");

    ticket.setBillNum(billNum);

    BillInfo billInfo = new BillInfo();
    billInfo.setEnterpriseId(enterpriseId);
    billInfo.setBillType(BILL_TYPE_TRAIN);
    billInfo.setBillNum(billNum);
    billInfo.setCustomer(customer);
    billInfo.setOpDate(opDate);

    billInfo.setOp1(operator);
    billInfo.setOperator(operator);

    billInfo.setPayTypeNote(PAY_TYPE_BILL + "");

    billInfo.setSupplier(supplier);
    billInfo.setPaymentMethod(payment);

    billInfo.setBillSum(ticket.getServiceCharge() + ticket.getPrice());
    billInfo.setCost(ticket.getPrice());
    billInfo.setProfit(ticket.getServiceCharge());

    billInfo.setTicketCount(1); // set ticket_count

    BillUtil.setBillCustomerInfo(billInfo, customerType, null, null, customer);

    int retCode = billDao.createTrainTicket(enterpriseId, operator, ticket, billInfo);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  public List<BillTrainTicket> searchTrainTickets(int enterpriseId, SearchBillInfoCriteria sc) {
    return billDao.searchTrainTickets(enterpriseId, sc);
  }

  @Override
  public JsonResult createHotelBooking(int enterpriseId, String operator, int customerEnterpriseId,
                                       int supplierId, int paymentId, BillHotelBooking o) {
    if (o.getPassengerName().length() == 0 ||
            o.getHotelName().length() == 0 ||
            o.getRoomType().length() == 0 ||
            o.getDdate() == null ||
            o.getLeaveDate() == null) {
      return JsonResult.createJsonResult(-2, "酒店入住数据不能留空");
    }

    if (o.getLeaveDate().getTime() <= o.getDdate().getTime()) {
      return JsonResult.createJsonResult(-5, "离店日期要晚于入住日期");
    }

    // 客户类型
    int customerType = customerEnterpriseId > 0 ? 1 : 0;

    Customer customer = customerDao.getCustomerById(customerEnterpriseId);
    if (customer == null || customer.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_CUSTOMER_NOT_EXISTED);
    }

    Supplier supplier = enterpriseDao.getSupplierById(supplierId);
    if (supplier == null || supplier.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-3, ERROR_MESSAGE_SUPPLIER_NOT_EXISTED);
    }

    PaymentMethod payment = enterpriseDao.getPaymentMethodById(paymentId);
    if (payment == null || payment.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-4, ERROR_MESSAGE_PAYMENT_METHOD_NOT_EXISTED);
    }

    final String billNum = orderNoGenerator.generateOrderNo(enterpriseId, OrderNoInfo.BILL_HOTEL);
    final Date opDate = DateUtil.getCurDate();

    o.setEnterpriseId(enterpriseId);
    o.setCustomer(customer);

    o.setSaleDate(opDate);
    o.setPayMode(PAY_TYPE_BILL + "");

    o.setBillNum(billNum);

    BillInfo billInfo = new BillInfo();
    billInfo.setEnterpriseId(enterpriseId);
    billInfo.setBillType(BILL_TYPE_HOTEL);
    billInfo.setBillNum(billNum);
    billInfo.setSupplier(supplier);
    billInfo.setPaymentMethod(payment);
    billInfo.setOpDate(opDate);

    billInfo.setOp1(operator);
    billInfo.setOperator(operator);

    billInfo.setPayTypeNote(PAY_TYPE_BILL + "");

    billInfo.setBillSum(o.getTotalAmount());
    billInfo.setCost(o.getTotalCost());
    billInfo.setProfit(o.getProfit());

    billInfo.setTicketCount(1);

    BillUtil.setBillCustomerInfo(billInfo, customerType, null, null, customer);

    int retCode = billDao.createHotelBooking(enterpriseId, operator, o, billInfo);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  public List<BillHotelBooking> searchHotelBookings(int enterpriseId, SearchBillInfoCriteria sc) {
    return billDao.searchHotelBookings(enterpriseId, sc);
  }





  /**
   * 查找未审核账单的机票
   *
   * @param enterpriseId 企业id
   * @param sc           查找条件
   */
  @Override
  @Secured({RoleInfo.ROLE_SUPERVISOR, RoleInfo.ROLE_CASHIER, RoleInfo.ROLE_ADMIN})
  public List<BillFlightTicket> searchUnreviewedFlightTickets(int enterpriseId,
                                                              SearchFlightOrderCriteria sc) {
    List<BillFlightTicket> tickets = billDao.searchBillFlightTickets(enterpriseId, sc, 0);
    for (BillFlightTicket ticket : tickets) {
      BillUtil.processRouteDetail(ticket.getRouteDetail(), ticket.getFlights());
    }

    return tickets;
  }

  /**
   * 查找已审核，未付款的机票
   */
  @Override
  @Secured({RoleInfo.ROLE_SUPERVISOR, RoleInfo.ROLE_CASHIER, RoleInfo.ROLE_ADMIN})
  public List<BillFlightTicket> searchUnpaidFlightTickets(int enterpriseId,
                                                          SearchFlightOrderCriteria sc) {
    List<BillFlightTicket> tickets = billDao.searchBillFlightTickets(enterpriseId, sc, 1);
    for (BillFlightTicket ticket : tickets) {
      BillUtil.processRouteDetail(ticket.getRouteDetail(), ticket.getFlights());
    }

    return tickets;
  }

  @Override
  @Secured({"ROLE_ADMIN"})
  public JsonResult deleteBillInfo(final int enterpriseId, final String username, final int id) {
    BillInfo billInfo = billDao.getBillInfo(id);
    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_BILL_NOT_EXISTED);
    }
    if (billInfo.getBePaid() != null && "1".equals(billInfo.getBePaid())) {
      return JsonResult.createJsonResult(-4, "此账单已核销");
    }

    // 已审核的账单也不允许删除
    if ("1".equalsIgnoreCase(billInfo.getAudited())) {
      return JsonResult.createJsonResult(-2, "账单已审核");
    }

    int retCode = billDao.deleteBillInfo(enterpriseId, id);
    if (retCode > 0) {
      doSendAlert(enterpriseId, "账单(" + billInfo.getBillNum() + ", " + billInfo.getOpDate() + ")被 " + username + " 删除.", null,
              EntDepartment.DEPT_ADMINISTRATOR);
    }
    return JsonResult.createJsonResult(retCode);
  }

  // 审核账单通过
  @Override
  @Secured({"ROLE_ADMIN", "ROLE_CASHIER"})
  public JsonResult auditBillInfoDone(int enterpriseId, String operator, int id) {
    int retCode = billDao.auditBillInfoDone(enterpriseId, operator, id, "审核通过（账单id)");
    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 账单审核通过（使用账单号）
   *
   * @param enterpriseId 企业id
   * @param operator     操作人用户名
   * @param billNum      账单号
   */
  @Override
  @Secured({"ROLE_ADMIN", "ROLE_CASHIER"})
  public JsonResult auditBillInfoDoneByBillNum(int enterpriseId, String operator, String billNum) {
    BillInfo billInfo = billDao.getBillInfo(enterpriseId, billNum);
    int retCode = billDao.auditBillInfoDone(enterpriseId, operator, billInfo.getId(), "审核通过(账单号)");
    return JsonResult.createJsonResult(retCode);
  }

  // 取消账单审核
  @Override
  @Secured({"ROLE_ADMIN", "ROLE_CASHIER"})
  public JsonResult cancelBillInfoAuditPassed(final int enterpriseId, String operator, int id,
                                              String remark) {
    BillInfo billInfo = billDao.getBillInfo(id);
    if (billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, "无此账单");
    }

    if (billInfo.getAudited().equalsIgnoreCase(BILL_NOT_AUDITED)) {
      return JsonResult.createJsonResult(-2, "账单未审核");
    }

    if (billInfo.getSettlementNo() != null && billInfo.getSettlementNo().trim().length() > 0) {
      return JsonResult.createJsonResult(-3, "已被结算单(" + billInfo.getSettlementNo() + ")包含。");
    }

    if (billInfo.getBePaid().equalsIgnoreCase(BillInfo.CUSTOMER_PAYMENT_RECEIVED)) {
      return JsonResult.createJsonResult(-4, "账单已销账");
    }

    if (remark == null || remark.trim().length() < 2) {
      return JsonResult.createJsonResult(-5, "请输入取消审核的原因");
    }

    int retCode = billDao.cancelBillInfoAuditPassed(enterpriseId, operator, id, remark);
    if (retCode > 0) {
      doSendAlert(enterpriseId, "账单(" + billInfo.getBillNum() + ")取消审核，取消原因：" + remark, null,
              EntDepartment.DEPT_ADMINISTRATOR);
    }

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 搜索单张机票信息
   */
  @Override
  public BillFlightTicket searchFlightTicket(int enterpriseId, String ticketNo) {
    if (ticketNo == null || (ticketNo.length() != 13 && ticketNo.length() != 14)) {
      return null;
    }

    String balCode = ticketNo.substring(0, 3);
    String tickNo = ticketNo.length() == 14 ? ticketNo.substring(4) : ticketNo.substring(3);

    return flightCharterDao.searchBillFlightTicket(enterpriseId, balCode, tickNo);
  }

  @Override
  @Secured({"ROLE_OPERATOR", "ROLE_SUPERVISOR", "ROLE_ADMIN"})
  public JsonResult createFlightRefundTicket(int enterpriseId, String ticketNo, String psgName,
                                             double accountRecv,
                                             double airRefundFee, double serviceCharge, String remark, String operator) {

    if (ticketNo == null || (ticketNo.length() != 13 && ticketNo.length() != 14)) {
      return JsonResult.createJsonResult(-1, "票号错误");
    }

    String balCode = ticketNo.substring(0, 3);
    String tickNo = ticketNo.length() == 14 ? ticketNo.substring(4) : ticketNo.substring(3);

    BillFlightTicket ticket = flightCharterDao
            .searchBillFlightTicket(enterpriseId, balCode, tickNo);
    if (ticket == null) {
      return JsonResult.createJsonResult(-2, "票号不存在");
    }

    BillFlightRefundTicket refundTicket = new BillFlightRefundTicket();

    refundTicket.setEnterpriseId(enterpriseId);
    refundTicket.setBalanceCode(balCode);
    refundTicket.setTicketNo(tickNo);

    refundTicket.setCustomer(ticket.getCustomer());

    refundTicket.setPsgName(psgName);

    refundTicket.setTicketAmount(accountRecv);
    refundTicket.setAirRefundCharge(airRefundFee);
    refundTicket.setServiceCharge(serviceCharge);
    refundTicket.setRemark(remark);
    refundTicket.setOperator(operator);

    int retCode = flightCharterDao.createFlightRefundTicket(refundTicket);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  public List<BillFlightRefundTicket> searchFlightRefundTickets(int enterpriseId,
                                                                CommonSearchCriteria sc) {
    return flightCharterDao.searchFlightRefundTickets(enterpriseId, sc);
  }

  /**
   * 修改账单的所属客户
   *
   * @param enterpriseId 操作员的enterpriseId
   * @param username     操作员的username(用户名)
   * @param billId       账单id
   * @param customerId   客户id
   */
  @Override
  public JsonResult changeBillCustomer(final int enterpriseId, final String username,
                                       final int billId, final int customerId, String costCenter, String projectName) {
    // 校验数据
    BillInfo billInfo = billDao.getBillInfo(billId);
    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_BILL_NOT_EXISTED);
    }

    if (customerId > 0) {
      Customer customer = customerDao.getCustomerById(customerId);
      if (customer == null || customer.getEnterpriseId() != enterpriseId) {
        return JsonResult.createJsonResult(-2, "不存在此客户");
      }
    }

    int retCode = billDao
            .changeBillCustomer(enterpriseId, username, billId, customerId, costCenter, projectName);

    if (retCode > 0) {
      return JsonResult.createJsonResult(retCode);
    } else {
      JsonResult jsonResult = JsonResult.createJsonResult(retCode);
      if (retCode == -100) {
        jsonResult.setErrmsg("保存数据时出现意外错误");
      } else if (retCode == -101) {
        jsonResult.setErrmsg("保存数据时出现逻辑错误");
      }
      return jsonResult;
    }
  }



  /**
   * 批量审核账单
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult batchReviewBillInfos(int enterpriseId, String username, int[] billIds,
                                         double total) {
    try {
      int retCode = billDao.batchReviewBillInfos(enterpriseId, username, billIds, total);
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  /**
   * 创建结算单
   *
   * @param enterpriseId   当前用户的enterpriseId
   * @param customerId     客户id
   * @param settlementId   结算单id
   * @param settlementNo   结算单号
   * @param settlementName 结算单名称
   * @param total          结算单金额
   * @param billIds        哪些账单进入此结算单
   * @return JsonResult
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult createSettlement(int enterpriseId, String username, int customerId,
                                     int settlementId, String settlementNo, String settlementName, double total,
                                     Integer[] billIds) {
    if (settlementId <= 0) {
      if (settlementNo == null || settlementName == null || settlementNo.trim().length() == 0
              || settlementName.trim().length() == 0) {
        return JsonResult.createJsonResult(-1, "结算单号，结算单名称都需要填写");
      }
    }

    try {
      int retCode = settlementDao
              .createSettlement(enterpriseId, username, customerId, settlementId, settlementNo,
                      settlementName, total, billIds, null);
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  /**
   * 批量创建结算单
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult batchCreateSettlement(final int enterpriseId, final String username,
                                          final Date beginDate, final Date endDate, final int customerId, String prefix) {
    if (beginDate == null || endDate == null || beginDate.getTime() > endDate.getTime()) {
      return JsonResult.createJsonResult(-1, "请选择日期范围");
    }

    // 日期范围不能超过365天
    final int maxDays = 365;
    if (DateUtil.getDaySpan(endDate, beginDate) > maxDays) {
      return JsonResult.createJsonResult(-2, "日期范围不能超过" + maxDays + "天");
    }

    if (customerId == 0) {
      //散客
      return JsonResult.createJsonResult(-3, "不支持此类型客户");
    }

    return doBatchCreateSettlement(enterpriseId, username, beginDate, endDate, customerId, prefix);
  }

  /**
   * 批量创建结算单
   */
  private JsonResult doBatchCreateSettlement(final int enterpriseId, final String username,
                                             final Date beginDate, final Date endDate,
                                             final int customerId0, final String customPrefix) {
    final int year = DateUtil.getYear(endDate);
    final int month = DateUtil.getMonth(endDate);
    final String snPrefix = StringUtils.isEmpty(customPrefix) ? String.format("%4d%02d", year, month) : customPrefix;

    SearchBillInfoCriteria sc = new SearchBillInfoCriteria();
    sc.setPageNo(1);
    sc.setPageSize(50000);
    sc.setBillType("-1");
    sc.setSupplierId(0);
    sc.setBePaid("0");  // 未支付的
    sc.setReviewed(1); // 查看已审核的账单
    sc.setSettled(0); // 未结算账单
    sc.setBeginDate(beginDate);
    sc.setEndDate(endDate);
    if (customerId0 > 0) {
      // 对指定客户进行批量创建订单
      sc.setCustomerId(customerId0);
    }

    List<BillInfo> billInfos = billDao.searchMyBillInfo(enterpriseId, sc);

    List<BillInfo> entBillInfos = billInfos.stream()
            .filter(b -> b.getCustomerType() == Customer.ENT_CORPORATION)
            .sorted(Comparator.comparing(BillInfo::getCustomerId))
            .collect(toList());

    int ssCount = 0;
    int errCode = 0;
    try {
      int customerId = 0;
      String vipName = "";
      String settlementNo = "";
      String settlementName = "";
      double total = 0;
      List<Integer> billIds = new ArrayList<>();
      for (BillInfo billInfo : entBillInfos) {
        if (customerId == 0) {
          //新的公司
          customerId = billInfo.getCustomerId();
          vipName = billInfo.getCustomer().getVipName();
          //结算单号
          settlementNo = snPrefix + "-" + vipName;
          //结算单名称
          settlementName = vipName + "-" + snPrefix;
          total = 0;
          billIds.clear();
        } else if (customerId != billInfo.getCustomerId()) {
          // 新的公司，把之前的先保存了
          int retCode = doCreateSettlement(enterpriseId, username, settlementNo, settlementName,
                  customerId, total, billIds, "批量创建");
          if (retCode > 0) {
            ssCount++;
          }

          // 数据重置
          customerId = billInfo.getCustomerId();
          vipName = billInfo.getCustomer().getVipName();
          //结算单号
          settlementNo = snPrefix + "-" + vipName;
          //结算单名称
          settlementName = vipName + "-" + snPrefix;
          total = 0;
          billIds.clear();
        }

        //同一个公司
        total += billInfo.getBillSum();
        billIds.add(billInfo.getId());
      }

      if (!billIds.isEmpty()) {
        int retCode = doCreateSettlement(enterpriseId, username, settlementNo, settlementName,
                customerId, total, billIds, "批量创建");
        if (retCode > 0) {
          ssCount++;
        }
      }
    } catch (Exception ex) {
      errCode = -100;
      ex.printStackTrace();
    }

    String content = "批量创建了 " + ssCount + " 个结算单";
    if (errCode == 0) {
      content += ".";
    } else {
      content += ", 但意外中止，请继续尝试。";
    }
    doSendAlert(enterpriseId, content, null, EntDepartment.DEPT_CASHIER);

    return JsonResult.createJsonResult(ssCount, content);
  }

  /**
   * 保存结算单
   *
   * @param settlementNo 结算单号
   */
  private int doCreateSettlement(final int enterpriseId, final String username, String settlementNo,
                                 String settlementName, int customerId, double total, List<Integer> billIds, String remark) {
    SettlementSheet ss = settlementDao.getSettlementSheetByNo(enterpriseId, settlementNo);
    int settlementId = ss != null ? ss.getId() : 0;

    Integer[] arrayBillIds = new Integer[1];
    arrayBillIds = billIds.toArray(arrayBillIds);
    int retCode;
    try {
      //结算单号的长度限制为50
      if (settlementNo.length() > 50) {
        settlementNo = settlementNo.trim().substring(0, 40);
      }
      retCode = settlementDao
              .createSettlement(enterpriseId, username, customerId, settlementId, settlementNo,
                      settlementName, total, arrayBillIds, remark);
    } catch (RuntimeException ex) {
      retCode = -100;
      doSendAlert(enterpriseId, remark + ": " + ex.getMessage(), null, EntDepartment.DEPT_CASHIER);
      ex.printStackTrace();
    }

    return retCode;
  }

  /**
   * 查找结算单
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_SUPERVISOR", "ROLE_ADMIN"})
  public List<SettlementSheet> searchSettlements(int enterpriseId, int userId, SearchSettlementCriteria sc) {
    if (isOnlySupervisor(userId)) {
      List<Integer> allowedCustomerIds = searchAllowedCustomerIds(enterpriseId, userId);
      if (allowedCustomerIds.isEmpty()) {
        return new ArrayList<>();
      } else {
        return settlementDao.searchSettlements(enterpriseId, sc, allowedCustomerIds);
      }
    } else {
      return settlementDao.searchSettlements(enterpriseId, sc, null);
    }
  }

  @Override
  @Secured({"ROLE_CASHIER", "ROLE_SUPERVISOR", "ROLE_ADMIN"})
  public SettlementDto searchSettlementDetail(final int enterpriseId, final int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      logger.error("Settlement({}) is null.", id);
      return null;
    }

    SettlementDto dto = SettlementDto.createBySettlement(ss);

    //查询包含的账单
    List<BillInfo> billInfos = billDao
            .searchBillInfoBySettlementNo(enterpriseId, ss.getSettlementNo(), null, null, null,
                    null, null);

    //排序（结算日期降序、账单类型、账单日期、账单ID）
    billInfos = billInfos.stream().sorted(
            Comparator.comparing(BillInfo::getSettleDate).reversed()
                    .thenComparing(BillInfo::getBillType)
                    .thenComparing(BillInfo::getOpDate)
                    .thenComparing(BillInfo::getId)
    ).collect(toList());


    dto.setBills(billInfos);

    return dto;
  }

  @Override
  public List<SettlementSheet> searchUnpaidSettlements(int enterpriseId, int customerId) {
    return settlementDao.searchUnpaidSettlements(enterpriseId, customerId);
  }

  /**
   * 从结算单中移除账单
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult removeBillOfSettlement(int enterpriseId, String username, int settlementId,
                                           String billNo) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(settlementId);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, "无此结算单");
    }

    BillInfo billInfo = billDao.getBillInfo(enterpriseId, billNo);
    if (billInfo == null) {
      return JsonResult.createJsonResult(-2, "无此账单");
    }

    if (billInfo.getSettlementNo() == null || !billInfo.getSettlementNo()
            .equalsIgnoreCase(ss.getSettlementNo())) {
      return JsonResult.createJsonResult(-3, "账单和结算单不匹配");
    }

    try {
      int retCode = settlementDao.removeBillOfSettlement(enterpriseId, username, settlementId, billNo);
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  @Override
  public List<OperationLog> searchSettlementHistory(int enterpriseId, int settlementId) {
    return settlementDao.searchSettlementHistory(enterpriseId, settlementId);
  }

  @Override
  public List<OperationLog> searchBillInfoHistory(int enterpriseId, int id) {
    return billDao.searchBillInfoHistory(enterpriseId, id);
  }

  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoSupplier(int enterpriseId, String username, int billId,
                                           int supplierId) {
    Supplier supplier = enterpriseDao.getSupplierById(supplierId);
    if (supplier == null || supplier.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_SUPPLIER_NOT_EXISTED);
    }

    int retCode = billDao.updateBillSupplier(enterpriseId, username, billId, supplier);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoPayment(int enterpriseId, String username, int billId,
                                          int payId) {
    PaymentMethod pm = enterpriseDao.getPaymentMethodById(payId);
    if (pm == null || pm.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_PAYMENT_METHOD_NOT_EXISTED);
    }

    int retCode = billDao.updateBillInfoPayment(enterpriseId, username, billId, pm);

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 修改账单的出票日期
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoEtdzDate(int enterpriseId, String username, int billId,
                                           Date etdzDate) {
    int retCode = billDao.updateBillInfoEtdzDate(enterpriseId, username, billId, etdzDate);

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 修改账单的账单日期
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoOpDate(int enterpriseId, String username, int billId,
                                         Date opDate) {
    int retCode = billDao.updateBillInfoOpDate(enterpriseId, username, billId, opDate);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoOp1(int enterpriseId, String username, int billId, String op1) {
    int retCode = billDao.updateBillInfoOp1(enterpriseId, username, billId, op1);

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 修改账单备注：任何员工，任何状态都可以修改备注
   */
  @Override
  public JsonResult updateBillInfoRemark(int enterpriseId, String username, int billId,
                                         String remark) {
    int retCode = billDao.updateBillInfoRemark(enterpriseId, username, billId, remark);

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult updateBillInfoPayType(int enterpriseId, String username, int billId,
                                          String payType) {
    int retCode = billDao.updateBillInfoPayType(enterpriseId, username, billId, payType);

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 自动审核账单
   */
  @Override
  @Secured({"ROLE_CASHIER", "ROLE_ADMIN"})
  public JsonResult autoReviewBillInfo(final int enterpriseId, final String username) {
    return execAutoReviewBillInfo(enterpriseId, username);
  }

  @Override
  public JsonResult autoReviewBillInfoByTask(final int enterpriseId, final String username) {
    return execAutoReviewBillInfo(enterpriseId, username);
  }

  /**
   * 执行自动审核账单的差额总数
   *
   * @param enterpriseId 企业id
   * @param username     当前用户
   */
  private JsonResult execAutoReviewBillInfo(final int enterpriseId, final String username) {
    int total = 0;
    int reviewedCount = execAutoReviewFlightBills(enterpriseId, username, BILL_TYPE_FLIGHT, TICKET_STATUS_PRINTED);
    if (reviewedCount > 0) {
      total += reviewedCount;
    }

    reviewedCount = execAutoReviewFlightBills(enterpriseId, username, BILL_TYPE_FLIGHT_CHANGE, TICKET_STATUS_EX);
    if (reviewedCount > 0) {
      total += reviewedCount;
    }

    reviewedCount = execAutoReviewFlightBills(enterpriseId, username, BILL_TYPE_FLIGHT_REFUND, TICKET_STATUS_REFUND);
    if (reviewedCount > 0) {
      total += reviewedCount;
    }

    JsonResult jsonResult = JsonResult.createJsonResult(total);
    if (total > 0) {
      jsonResult.setDesc("自动审核了 " + total + " 个账单");
    }

    final Date previewDate = DateUtil.getYesterday();
    this.autoPreviewBills(enterpriseId, username, previewDate, FLIGHT_AUTO_TICKETING_BSP);

    return jsonResult;
  }

  /**
   * 对机票账单进行自动审核
   */
  private int execAutoReviewFlightBills(final int enterpriseId, final String username, final String billType,
                                        final int ticketStatus) {
    int retCode = 0;
    List<BillInfo> billInfos = doSearchBillsToReview(enterpriseId, billType);

    final Date minDate = DateUtil.addDays(DateUtil.getCurDate(), -30);
    for (BillInfo billInfo : billInfos) {
      //超过30天的，不考虑
      if (billInfo.getOpDate().getTime() < minDate.getTime()) {
        continue;
      }

      retCode += doReviewFlightBill(enterpriseId, username, ticketStatus, billInfo);
    }

    return retCode;
  }

  private int doReviewFlightBill(final int enterpriseId, final String username, final int ticketStatus, BillInfo billInfo) {
    int retCode = 0;

    if (ticketStatus == TICKET_STATUS_PRINTED) {
      retCode = doReviewPrintedFlightBill(enterpriseId, username, billInfo, ticketStatus);
    } else if (ticketStatus == TICKET_STATUS_EX) {
      retCode = doReviewExchangeFlightBill(enterpriseId, username, billInfo, ticketStatus);
    } else if (ticketStatus == TICKET_STATUS_REFUND) {
      retCode = doReviewRefundFlightBill(enterpriseId, username, billInfo, ticketStatus);
    }

    return retCode;
  }

  private int doReviewRefundFlightBill(final int enterpriseId, final String username, BillInfo billInfo, final int ticketStatus) {
    List<BillFlightRefundTicket> tickets = billDao.searchBillFlightRefundTickets(enterpriseId, billInfo.getBillNum());
    billInfo.setRefundTickets(tickets);
    if (billInfo.getRefundTickets().isEmpty()) {
      logger.error("bill info's refund-tickets is empty. bill No. is {}", billInfo.getBillNum());
      return 0;
    }

    //记录账单中的票号底价综合
    double totalCost0 = 0;
    //记录账单所包含的票号在TicketSale中的底价总和
    double totalCost1 = 0;
    boolean b = true;

    for (BillFlightRefundTicket t : billInfo.getRefundTickets()) {
      totalCost0 += t.getAirlineRefundAmount();

      List<TicketSale> results = flightCharterDao.searchTprs(enterpriseId, t.getBalanceCode() + "-" + t.getTicketNo(), ticketStatus);
      if (results.size() == 1) {
        totalCost1 += results.get(0).getPrice();
      } else {
        logger.warn("{}-{}, {} can not be found.", t.getBalanceCode(), t.getTicketNo(), ticketStatus);
        b = false;
        break;
      }
    }

    if (b) {
      return saveBillReviewResult(enterpriseId, username, billInfo, ticketStatus, totalCost0, totalCost1);
    }
    return 0;
  }

  private int doReviewExchangeFlightBill(final int enterpriseId, final String username, BillInfo billInfo, final int ticketStatus) {
    List<BillChangeOrder> orders = billDao.searchBillChangeOrders(enterpriseId, billInfo.getBillNum());
    for (BillChangeOrder t : orders) {
      BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
    }
    billInfo.setChangeOrders(orders);
    if (billInfo.getChangeOrders().isEmpty()) {
      logger.error("bill info's change-orders is empty. bill No. is {}",  billInfo.getBillNum());
      return 0;
    }

    //记录账单中的票号底价综合
    double totalCost0 = 0;
    //记录账单所包含的票号在TicketSale中的底价总和
    double totalCost1 = 0;
    boolean b = true;

    for (BillChangeOrder co : billInfo.getChangeOrders()) {
      totalCost0 += co.getAirChangeCharge();

      List<TicketSale> results = flightCharterDao.searchTprs(enterpriseId, co.getBalanceCode() + "-" + co.getTicketNo(), ticketStatus);
      if (results.isEmpty() && co.getNewTicketNo().length() == 14) {
        //用改签单的新票号试试
        results = flightCharterDao.searchTprs(enterpriseId, co.getNewTicketNo(), ticketStatus);
      }

      if (results.size() == 1) {
        totalCost1 += results.get(0).getTax() + results.get(0).getPrice();
      } else {
        logger.warn("{}-{}, {} can not be found.", co.getBalanceCode(), co.getTicketNo(), ticketStatus);
        b = false;
        break;
      }
    }

    if (b) {
      return saveBillReviewResult(enterpriseId, username, billInfo, ticketStatus, totalCost0, totalCost1);
    }
    return 0;
  }

  private int doReviewPrintedFlightBill(final int enterpriseId, final String username, BillInfo billInfo, final int ticketStatus) {
    this.addBillFlightTickets(billInfo);

    if (billInfo.getTickets().isEmpty()) {
      logger.error("bill info's tickets is empty. bill No. is {}", billInfo.getBillNum());
      return 0;
    }

    //记录账单中的票号底价综合
    double totalCost0 = 0;
    //记录账单所包含的票号在TicketSale中的底价总和
    double totalCost1 = 0;
    boolean b = true;

    for (BillFlightTicket ticket : billInfo.getTickets()) {
      totalCost0 += ticket.getCost();

      List<TicketSale> results = flightCharterDao.searchTprs(enterpriseId, ticket.getBalanceCode() + "-" + ticket.getTicketNo(), ticketStatus);

      if (results.size() == 1) {
        totalCost1 += results.get(0).getBottomPrice();
      } else {
        b = false;
        break;
      }
    }

    if (b) {
      return saveBillReviewResult(enterpriseId, username, billInfo, ticketStatus, totalCost0, totalCost1);
    }
      return 0;
  }

  private int saveBillReviewResult(final int enterpriseId, final String username, BillInfo billInfo,
                                   final int ticketStatus, final double totalCost0, final double totalCost1) {
    final double alpha = Math.abs(totalCost0 - totalCost1);
    if (alpha < 0.00000001) {
      //可以自动审核
      if (billDao.autoReviewBillInfo(enterpriseId, username, billInfo.getId(), ticketStatus) > 0) {
        return 1;
      }
    } else {
      // 记录到对应的账单的备注中
      String remark = "自审：有误差(" + DecimalUtil.keepScale2(alpha) + ").";
      remark = StringUtil.mergedRemark(billInfo.getRemark(), remark);
      billDao.updateBillInfoRemark(enterpriseId, username, billInfo.getId(), remark);
    }

    return 0;
  }

  private List<BillInfo> doSearchBillsToReview(final int enterpriseId, final String billType) {
    SearchBillInfoCriteria sc = new SearchBillInfoCriteria();
    sc.setPageSize(5000);
    sc.setBillType(billType);
    sc.setReviewed(0); //未审核
    sc.setCustomerId(-1); //所有客户

    return billDao.searchMyBillInfo(enterpriseId, sc);
  }





  @Override
  public List<BillInfo> searchSettlementBills(int enterpriseId, int id, String billNo,
                                              String orderNo, String strBillDate, String flightOrderNo, String psgName) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    Date billDate = null;
    if (strBillDate != null && strBillDate.trim().length() == 10) {
      try {
        billDate = DateUtil.convertToDate(strBillDate, DATE_YYYY_MM_DD);
      } catch (ParseException e) {
        logger.error(e.getMessage());
      }
    }

    //查询包含的账单
    return billDao.searchBillInfoBySettlementNo(enterpriseId, ss.getSettlementNo(), billNo, orderNo,
            billDate, flightOrderNo, psgName);
  }




  @Override
  public List<BillFlightTicket> searchSettlementBillFlightTicket(int enterpriseId, int id,
                                                                 String psgName, String ticketNo) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    return billDao
            .searchBillFlightTicketsBySettlementNo(enterpriseId, ss.getSettlementNo(), psgName,
                    ticketNo);
  }

  /**
   * 查找结算单中包含的退票票号信息
   */
  @Override
  public List<BillFlightRefundTicket> searchSettlementRefunds(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    List<BillFlightRefundTicket> refunds = billDao
            .searchRefundsBySettlementNo(enterpriseId, ss.getSettlementNo());

    for (BillFlightRefundTicket t : refunds) {
      BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
    }

    return refunds;
  }

  @Override
  public List<BillChangeOrder> searchSettlementChanges(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    List<BillChangeOrder> changeOrders = billDao
            .searchChangesBySettlementNo(enterpriseId, ss.getSettlementNo());

    for (BillChangeOrder t : changeOrders) {
      BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
    }

    return changeOrders;
  }

  @Override
  public List<BillVasOrder> searchSettlementVases(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    return billDao.searchVasOrdersBySettlementNo(enterpriseId, ss.getSettlementNo());
  }

  @Override
  public List<BillHotelBooking> searchSettlementHotels(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    return billDao.searchHotelOrdersBySettlementNo(enterpriseId, ss.getSettlementNo());
  }

  @Override
  public List<BillTrainTicket> searchSettlementTrains(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    return billDao.searchTrainTicketsBySettlementNo(enterpriseId, ss.getSettlementNo());
  }

  @Override
  public List<BillRentalCarOrder> searchSettlementBillRentalCars(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return new ArrayList<>();
    }

    return billDao.searchBillRentalCarsBySettlementNo(enterpriseId, ss.getSettlementNo());
  }



  /**
   * 修改结算单名称
   */
  @Override
  @Secured({RoleInfo.ROLE_ADMIN, RoleInfo.ROLE_CASHIER})
  public JsonResult updateSettlementName(int enterpriseId, String username, int id,
                                         String settlementName) {
    return JsonResult
            .createJsonResult(settlementDao.updateSettlementName(enterpriseId, username, id, settlementName));
  }

  /**
   * 根据结算单内容生成报表
   */
  @Override
  public SettlementReportDto searchSettlementReport(int enterpriseId, int id) {
    SettlementSheet ss = settlementDao.getSettlementSheetById(id);
    if (ss == null || ss.getEnterpriseId() != enterpriseId) {
      return null;
    }

    SettlementReportDto dto = SettlementReportDto.createBySettlement(ss);
    if (dto.getCustomer() == null) {
      dto.setReportTemplateId(1);
      dto.setCustomer(CustomerDto.create(0, "散客", ""));
    } else if (dto.getReportTemplateId() == 0 && dto.getCustomer().getId() > 0) {
      //去获取该客户所使用的结算单报表模板
      Customer c = customerDao.getCustomerById(dto.getCustomer().getId());
      dto.setReportTemplateId(c.getReportTemplateId());
    }

    //机票
    List<ReportTicketDto> ticketDtoList = prepareSettlementReportTickets(enterpriseId,
            ss.getSettlementNo());

    //退票
    List<ReportTicketRefundDto> refunds = prepareSettlementReportRefunds(enterpriseId,
            ss.getSettlementNo(), ticketDtoList);

    //改签
    List<ReportTicketChangeDto> changes = prepareSettlementReportChanges(enterpriseId,
            ss.getSettlementNo(), ticketDtoList);

    //服务
    List<BillVasOrder> vasOrders = billDao.searchVasOrdersBySettlementNo(enterpriseId, ss.getSettlementNo());
    vasOrders = vasOrders.stream().filter(t -> t.getTotal() != 0).collect(toList());

    List<SettlementReportVasDto> vasDtos = new ArrayList<>();
    for (var vasOrder : vasOrders) {
      SettlementReportVasDto dto1 = SettlementReportVasDto.createByBillVas(vasOrder);
      //TODO 此处获取FLIGHT ORDER NO，以后用定时任务将关联的机票订单号保存好
      //TODO 15013930在账单中保存的乘客姓名有误，需要从原始订单中获取
      BillInfo billInfo = billDao.getBillInfo(enterpriseId, dto1.getBillNum());
      if (billInfo != null) {
        dto1.setFlightOrderNo(billInfo.getFlightOrderNo());
        if (vasOrder.getProductType() == INSURANCE) {
          VasOrder vasOrder1 = vasOrderDao.getOrderByOrderNo(enterpriseId, billInfo.getOrderNo());
          if (vasOrder1 != null) {
            StringBuilder name = null;
            for (VasOrderPassenger psg : vasOrder1.getPassengers()) {
              if (name == null) {
                name = new StringBuilder(psg.getName());
              } else {
                name.append(";").append(psg.getName());
              }
              dto1.getPassengers().add(psg.getName().trim());
            }
            if (name != null) {
              dto1.setPassengerName(name.toString());
            }
          }
        }
      }
      vasDtos.add(dto1);
    }

    //酒店
    List<BillHotelBooking> hotels = billDao
            .searchHotelOrdersBySettlementNo(enterpriseId, ss.getSettlementNo());

    //火车票
    List<BillTrainTicket> trains = billDao
            .searchTrainTicketsBySettlementNo(enterpriseId, ss.getSettlementNo());

    //租车
    List<BillRentalCarOrder> carOrders = billDao.searchBillRentalCarsBySettlementNo(enterpriseId, ss.getSettlementNo());
    for (BillRentalCarOrder carOrder : carOrders) {
      final double oldTotal = carOrder.getTotal();
      final double newTotal = carOrder.getPrice() - carOrder.getDiscount() + carOrder.getServiceCharge();
      if (newTotal != oldTotal) {
        logger.warn("{}: {} != {}", carOrder.getBillNum(), newTotal, oldTotal);
        carOrder.setTotal(newTotal);
      }
    }

    List<Airport> airports = basicDataDao.searchAirport(null);
    //处理机场中文名
    for (ReportTicketDto t : ticketDtoList) {
      processAirportName1(t.getDetails(), airports);
      cleanFakeTicketNo(t);
    }

    for (ReportTicketChangeDto t : changes) {
      processAirportName1(t.getDetails(), airports);
      cleanFakeTicketNo(t);
    }

    for (ReportTicketRefundDto t : refunds) {
      processAirportName1(t.getDetails(), airports);
      cleanFakeTicketNo(t);
    }

    for (SettlementReportVasDto t : vasDtos) {
      processAirportName1(t.getDetails(), airports);
    }

    dto.setTickets(ticketDtoList);
    dto.setRefunds(refunds);
    dto.setChanges(changes);
    dto.setVasOrders(vasDtos);
    dto.setHotels(hotels);
    dto.setTrains(trains);
    dto.setCarOrders(carOrders);

    return dto;
  }

  /**
   * 将系统虚拟的票号置空
   */
  private void cleanFakeTicketNo(ReportTicketDto t) {
    if (t.getTicketNo() != null && t.getTicketNo().contains("00000")) {
      t.setTicketNo("");
    }
  }

  /**
   * 临时代码: 用于将之前没有保存的pnr_no保存到退票账单中来。
   */
  @Override
  public void updateBillRefundTicketPnrNo(String agentName) {
    billDao.updateBillRefundTicketPnrNo(agentName);
  }

  @Override
  public void autoUpdateBillVas(String agentName) {
    billDao.updateBillVasRouteDetail(agentName);
    int count = 100;
    while (count == 100) {
      count = billDao.updateBillVasPsgCount(agentName);
      logger.info("updateBillVasPsgCount: {}", count);
    }
  }


  /**
   * 需要超级管理员权限
   */
  @Override
  @Secured({RoleInfo.ROLE_ADMIN})
  public JsonResult writeOffBill(final int enterpriseId, final int userId, final int id) {
    if (userId != enterpriseId) {
      return JsonResult.createJsonResult(-1, "权限不足");
    }

    BillInfo billInfo = billDao.getBillInfo(id);

    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-2, ERROR_MESSAGE_BILL_NOT_EXISTED);
    }

    if (BillInfo.CUSTOMER_PAYMENT_RECEIVED.equalsIgnoreCase(billInfo.getBePaid())) {
      return JsonResult.createJsonResult(-3, "此账单已核销");
    }

    if ("0".equalsIgnoreCase(billInfo.getAudited())) {
      return JsonResult.createJsonResult(-4, "此账单未审核");
    }

    if (!StringUtils.isEmpty(billInfo.getSettlementNo())) {
      return JsonResult
              .createJsonResult(-5, "此账单已归于结算单(" + billInfo.getSettlementNo() + "), 请通过该结算单销账");
    }

    int retCode = billDao.writeOffBill(enterpriseId, userId, billInfo.getId());

    return JsonResult.createJsonResult(retCode);
  }

  @Override
  public void autoPreviewBills(final int enterpriseId, String username, final Date billDate, final int supplierBspType) {
    List<Supplier> list = enterpriseDao.searchSupplierByTicketingType(enterpriseId, supplierBspType);
    if (list.isEmpty()) {
      return;
    }

    final Supplier supplier = list.get(0);
    //1. 校验正常票
    execPreviewNormalTickets(enterpriseId, username, billDate, supplier.getId(), 0);
    //2. 校验退票
    execPreviewNormalTickets(enterpriseId, username, billDate, supplier.getId(), 4);
  }

  private void execPreviewNormalTickets(final int enterpriseId, final String username, final Date billDate,
                                        final int supplierId, final int ticketStatus) {
    SearchBillInfoCriteria sc = new SearchBillInfoCriteria();
    sc.setPageSize(5000);
    sc.setBePaid(null);
    sc.setPayType(0);
    sc.setBillType("-1"); //
    sc.setReviewed(-1); //未审核和已审核
    sc.setCustomerId(-1); //所有客户
    sc.setPaymentStatus(-1);
    sc.setEtdzDateBegin(billDate);
    sc.setEtdzDateEnd(billDate);
    sc.setSupplierId(supplierId);
    sc.setTicketStatus(ticketStatus + "");

    List<BillInfo> bills = this.searchMyBillInfo(enterpriseId, sc, -1, true);
    double total0 = 0;
    Set<String> billTickets = new HashSet<>();
    for (var billInfo : bills) {
      total0 += billInfo.getCost();
      preparePreviewNormalTickets(billInfo, billTickets);
    }

    SearchTprCriteria sc1 = new SearchTprCriteria();
    sc1.setPageNo(1);
    sc1.setPageSize(2000 * 40);
    sc1.setBeginDate(billDate);
    sc1.setEndDate(billDate);
    sc1.setTicketStatus(ticketStatus + "");
    List<TicketSale> tprs = flightCharterDao.searchTprs(enterpriseId, null, sc1);
    double total1 = 0;

    Set<String> onlyTprTickets = new HashSet<>();
    for (TicketSale ts : tprs) {
      if (ticketStatus == 0) {
        total1 += ts.getPrice() - ts.getCommission() + ts.getTax();
      } else if (ticketStatus == 4) {
        total1 -= ts.getPrice();
      }

      final String ticketNo = ts.getTicketNo().replace("-", "");
      if (billTickets.contains(ticketNo)) {
        billTickets.remove(ticketNo);
      } else {
        onlyTprTickets.add(ticketNo);
      }
    }

    String opDate = DateUtil.formatDate(billDate, "yyyy-MM-dd");
    StringBuilder sb = new StringBuilder(username);
    var beta = preparePreviewNormalTicketMessage(sb, ticketStatus, opDate, total0, total1);


    doSendAlert(enterpriseId, sb.toString(), null, EntDepartment.DEPT_CASHIER);

    if (Math.abs(beta) > 0.001) {
      var content = new StringBuilder();
      if (ticketStatus == 0) {
        content.append("(正常票预审) ");
      } else if (ticketStatus == 4) {
        content.append("(退票预审) ");
      }
      if (!billTickets.isEmpty()) {
        content.append("仅账单包含的票号：").append(billTickets).append(";");
      }
      if (!onlyTprTickets.isEmpty()) {
        content.append("仅TPR包含的票号：").append(onlyTprTickets).append(";");
      }

      doSendAlert(enterpriseId, content.toString(), null, EntDepartment.DEPT_CASHIER);
    }
  }

  private double preparePreviewNormalTicketMessage(StringBuilder sb, int ticketStatus, String opDate,
                                                 double total0, double total1) {
    if (ticketStatus == 0) {
      sb.append(" 正常票预审(");
    } else if (ticketStatus == 4) {
      sb.append(" 退票预审(");
    }
    sb.append(opDate).append(", BSP): 账单总成本=").append(total0).append(", ");
    if (ticketStatus == 0) {
      sb.append("TPR总应付=");
    } else if (ticketStatus == 4) {
      sb.append("TPR总应退=");
    }
    //TODO 能否把有差额的账单号一起发送出去？ yfdai 2021-7-10
    final double beta = DecimalUtil.keepScale2(total0 - total1);
    sb.append(total1).append(", 相差=").append(beta).append(".");

    return beta;
  }

  /**
   * 为预审准备票号，有时候改签和出票是同一个票号，这样该票号则只计算一次
   */
  private void preparePreviewNormalTickets(BillInfo billInfo, Set<String> billTickets) {
    if (billInfo.getBillType().equalsIgnoreCase(BILL_TYPE_FLIGHT)) {
      prepareBillFlightTicket(billInfo, billTickets);
    } else if (BILL_TYPE_FLIGHT_CHANGE.equalsIgnoreCase(billInfo.getBillType())) {
      for (BillChangeOrder ticket : billInfo.getChangeOrders()) {
        if (StringUtils.isEmpty(ticket.getNewTicketNo())) {
          billTickets.add(ticket.getBalanceCode() + ticket.getTicketNo());
        } else {
          billTickets.add(ticket.getNewTicketNo().replace("-", ""));
        }
      }
    } else if (BILL_TYPE_FLIGHT_REFUND.equalsIgnoreCase(billInfo.getBillType())) {
      for (BillFlightRefundTicket ticket : billInfo.getRefundTickets()) {
        billTickets.add(ticket.getBalanceCode() + ticket.getTicketNo());
      }
    }
  }

  private void prepareBillFlightTicket(BillInfo billInfo, Set<String> billTickets) {
    for (var ticket : billInfo.getTickets()) {
      String ticketNo = ticket.getBalanceCode() + ticket.getTicketNo();
      if (ticket.getTicketCount() == 1) {
        billTickets.add(ticketNo);
      } else {
        var tickets = FlightTicketUtil.processTicketNo(ticketNo, ticket.getTicketCount());
        for (String t : tickets) {
          billTickets.add(t.replace("-", ""));
        }
      }
    }
  }


  @Override
  @Secured({RoleInfo.ROLE_ADMIN})
  public JsonResult updateBillPaymentStatus(int enterpriseId, int userId, int id, final int paymentStatus) {
    if (userId != enterpriseId) {
      return JsonResult.createJsonResult(-1, "权限不足");
    }

    BillInfo billInfo = billDao.getBillInfo(id);

    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-2, ERROR_MESSAGE_BILL_NOT_EXISTED);
    }

    if (paymentStatus == billInfo.getPaymentStatus()) {
      return JsonResult.createJsonResult(-3, "支付状态未变化");
    }

    if ("0".equalsIgnoreCase(billInfo.getAudited())) {
      return JsonResult.createJsonResult(-4, "此账单未审核");
    }

    int retCode = billDao.updateBillPaymentStatus(enterpriseId, userId, billInfo.getId(), paymentStatus);

    return JsonResult.createJsonResult(retCode);
  }

  /**
   * 准备结算单报表的机票部分
   */
  private List<ReportTicketDto> prepareSettlementReportTickets(final int enterpriseId,
                                                               final String settlementNo) {
    //机票
    List<BillFlightTicket> tickets = billDao
            .searchTicketsBySettlementNo(enterpriseId, settlementNo);

    List<ReportTicketDto> ticketDtoList = new ArrayList<>();
    for (BillFlightTicket ticket : tickets) {
      ReportTicketDto dto1 = ReportTicketDto.createReportTicketDto(ticket);
      //TODO 此处保存flightOrderNo, 方便后期与关联的保险订单合并
      BillInfo billInfo = billDao.getBillInfo(enterpriseId, dto1.getBillNum());
      if (billInfo != null) {
        dto1.setFlightOrderNo(billInfo.getFlightOrderNo());
      }
      ticketDtoList.add(dto1);
    }

    // 按订单日期排序
    ticketDtoList = ticketDtoList.stream()
            .sorted(Comparator.comparing(ReportTicketDto::getOrderDate)
                    .thenComparing(ReportTicketDto::getPassengerName)
                    .thenComparing(ReportTicketDto::getDdate))
            .collect(toList());

    return ticketDtoList;
  }

  /**
   * 准备结算单报表的退票部分（退票部分：如果票号在机票部分也出现了，则将出票和退票的票号内容放在一起）
   */
  private List<ReportTicketRefundDto> prepareSettlementReportRefunds(final int enterpriseId,
                                                                     final String settlementNo, List<ReportTicketDto> tickets) {
    List<BillFlightRefundTicket> refunds0 = billDao
            .searchRefundsBySettlementNo(enterpriseId, settlementNo);
    for (BillFlightRefundTicket t : refunds0) {
      BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
    }

    List<ReportTicketRefundDto> refunds = new ArrayList<>();
    for (BillFlightRefundTicket refundOrder : refunds0) {
      ReportTicketRefundDto dto1 = ReportTicketRefundDto.createReportRefundDto(refundOrder);
      refunds.add(dto1);
    }

    refunds = refunds.stream().sorted(Comparator.comparing(ReportTicketRefundDto::getTicketNo))
            .collect(toList());

    //如果同时包含出票和退票信息，则放置在一起
    boolean bContinue = true;
    while (bContinue) {
      bContinue = false;
      ReportTicketRefundDto refundTicketRemoved = null;

      for (ReportTicketRefundDto refundTicket : refunds) {
        for (ReportTicketDto ticketDto : tickets) {
          if (ticketDto.getTicketNo().equalsIgnoreCase(refundTicket.getTicketNo()) &&
                  ticketDto.getBalanceCode().equalsIgnoreCase(refundTicket.getBalanceCode())) {
            bContinue = true;

            refundTicketRemoved = refundTicket;

            int idx = tickets.indexOf(ticketDto) + 1;
            insertReportTickets(tickets, refundTicket, 1, idx);

            break;
          }
        }

        if (refundTicketRemoved != null) {
          break;
        }
      }

      if (refundTicketRemoved != null) {
        refunds.remove(refundTicketRemoved);
      }
    }

    return refunds;
  }

  private void insertReportTickets(List<ReportTicketDto> tickets, ReportTicketDto inserted,
                                   final int ticketType, final int insertIndex) {
    ReportTicketDto ticketDto1 = new ReportTicketDto();
    BeanUtils.copyProperties(inserted, ticketDto1);

    ticketDto1.setTicketType(ticketType); // 退票
    ticketDto1.setDetails(null);

    for (ReportTicketDetailDto fltInfo : inserted.getDetails()) {
      ReportTicketDetailDto detailDto = new ReportTicketDetailDto();
      BeanUtils.copyProperties(fltInfo, detailDto);

      ticketDto1.getDetails().add(detailDto);
    }

    if (insertIndex < tickets.size()) {
      tickets.add(insertIndex, ticketDto1);
    } else {
      tickets.add(ticketDto1);
    }
  }


  private void processAirportName1(List<ReportTicketDetailDto> details, List<Airport> airports) {
    processAirportName1(details, airports, 1);
  }

  private void processAirportName1(List<ReportTicketDetailDto> details, List<Airport> airports,
                                   final int format) {
    for (ReportTicketDetailDto d : details) {
      d.setDportName(getAirportName(d.getDport(), airports, format));
      d.setAportName(getAirportName(d.getAport(), airports, format));
    }
  }

  private void processAirportName2(List<FlightInfo> flights, List<Airport> airports) {
    for (FlightInfo d : flights) {
      if (d.getDepartureAirportName() == null || d.getDepartureAirportName().trim().length() == 0) {
        d.setDepartureAirportName(getAirportName(d.getDepartureAirport(), airports));
      }
      if (d.getArrivalAirportName() == null || d.getArrivalAirportName().trim().length() == 0) {
        d.setArrivalAirportName(getAirportName(d.getArrivalAirport(), airports));
      }
    }
  }

  /**
   * 准备结算单报表的改签部分 （改签部分：如果票号在机票部分也出现了，则将出票和改签的票号内容放在一起）
   *
   * @param enterpriseId 企业id
   * @param settlementNo 结算单号
   * @param tickets      已有的机票列表
   */
  private List<ReportTicketChangeDto> prepareSettlementReportChanges(final int enterpriseId,
                                                                     final String settlementNo, List<ReportTicketDto> tickets) {
    final List<BillChangeOrder> changes0 = billDao
            .searchChangesBySettlementNo(enterpriseId, settlementNo);
    for (BillChangeOrder t : changes0) {
      BillUtil.processRouteDetail(t.getRemark(), t.getFlights());
    }

    List<ReportTicketChangeDto> changeDtos = new ArrayList<>();
    for (BillChangeOrder changeOrder : changes0) {
      ReportTicketChangeDto dto1 = ReportTicketChangeDto.createReportChangeDto(changeOrder);
      changeDtos.add(dto1);
    }

    changeDtos = changeDtos.stream()
            .sorted(Comparator.comparing(ReportTicketChangeDto::getTicketNo)).collect(toList());
    //如果同时包含出票和改签，则放置在一起
    boolean bContinue = true;
    while (bContinue) {
      bContinue = false;
      ReportTicketChangeDto changeRemoved = null;

      for (ReportTicketChangeDto changeDto : changeDtos) {
        for (ReportTicketDto ticketDto : tickets) {
          if (ticketDto.getTicketNo().equalsIgnoreCase(changeDto.getTicketNo()) &&
                  ticketDto.getBalanceCode().equalsIgnoreCase(changeDto.getBalanceCode())) {
            bContinue = true;

            changeRemoved = changeDto;

            int idx = tickets.indexOf(ticketDto) + 1;
            insertReportTickets(tickets, changeDto, 2, idx);

            break;
          }
        }

        if (changeRemoved != null) {
          break;
        }
      }

      if (changeRemoved != null) {
        changeDtos.remove(changeRemoved);
      }
    }

    return changeDtos;
  }


  /**
   * 将结算号从账单中删除（也就是从结算单中移除账单）
   *
   * @param id 账单id
   */
  @Override
  @Secured({RoleInfo.ROLE_ADMIN, RoleInfo.ROLE_CASHIER})
  public JsonResult deleteSettlementNo(int enterpriseId, String username, int id) {

    BillInfo billInfo = billDao.getBillInfo(id, false);

    if (billInfo == null || billInfo.getEnterpriseId() != enterpriseId) {
      return JsonResult.createJsonResult(-1, ERROR_MESSAGE_BILL_NOT_EXISTED);
    }

    if (billInfo.getBePaid().equalsIgnoreCase(BillInfo.CUSTOMER_PAYMENT_RECEIVED)) {
      return JsonResult.createJsonResult(-2, "账单已销账");
    }

    if (StringUtils.isEmpty(billInfo.getSettlementNo())) {
      return JsonResult.createJsonResult(-3, "账单中没有结算单号");
    }

    // 根据结算单号找结算单
    SettlementSheet ss = settlementDao
            .getSettlementSheetByNo(enterpriseId, billInfo.getSettlementNo().trim());
    if (ss == null) {
      return JsonResult.createJsonResult(-4, "无此结算单");
    }

    try {
      int retCode = settlementDao
              .removeBillOfSettlement(enterpriseId, username, ss.getId(), billInfo.getBillNum());
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  @Override
  @Secured({RoleInfo.ROLE_ADMIN})
  public JsonResult adjustSettlementAmount(int enterpriseId, int userId, String username, int id,
                                           double amount, String remark) {
    if (userId != enterpriseId) {
      return JsonResult.createJsonResult(-1, "无权限");
    }
    if (amount <= 0) {
      return JsonResult.createJsonResult(-2, "扣除金额只能为正数");
    }

    int retCode = settlementDao.adjustSettlementAmount(enterpriseId, username, id, amount, remark);
    return JsonResult.createJsonResult(retCode);
  }

  private static String getAirportName(final String threeCode, final List<Airport> airports) {
    return getAirportName(threeCode, airports, 0);
  }

  /**
   * @param format 1-表示SHA PVG只显示虹桥，浦东
   */
  private static String getAirportName(final String threeCode, final List<Airport> airports,
                                       final int format) {
    String retVal = "";
    for (Airport airport : airports) {
      if (threeCode.equalsIgnoreCase(airport.getThreeCode())) {
        String temp = getAirportName(airport);

        if (format == 1 && ("SHA".equalsIgnoreCase(threeCode) || "PVG".equalsIgnoreCase(threeCode))) {
            temp = airport.getNameCN();
        }

        retVal = temp;
        break;
      }
    }

    return retVal;
  }

  @Override
  public void exportDefaultFormatBills(SearchBillInfoCriteria sc, final int format,
                                       final int enterpriseId, final int customerId,
                                       List<Airport> airports, List<Customer> customers,
                                       PrintWriter out) {
    this.exportBillDefaultFormat(sc, format, enterpriseId, customerId, airports, out);
  }

  /**
   * 为结算单报表格式三准备数据（机票+保险模式）
   *
   */
  @Override
  public void prepareSettlementReportTpl3(SettlementReportDto dto) {
    for (var vasDto : dto.getVasOrders()) {
      if (vasDto.getFlightOrderNo() == null || vasDto.getProductType() != INSURANCE) {
        continue;
      }



      for (ReportTicketDto t : dto.getTickets()) {
        if (vasDto.getFlightOrderNo().equalsIgnoreCase(t.getFlightOrderNo())
                && vasDto.getPassengerName().contains(t.getPassengerName())
        ) {
          //TODO  如果vasDto是多个人的退票利润调整单，但是这多个人的退票没有全部包含在该结算单中，则会导出该vasDto的数值部分的导出了 yfdai 2021-10-07
          if (t.getInsurance() > 0 || t.getInsurance() < 0) {

          } else if (vasDto.getPrice() < 0 && t.getTicketType() == 1) {
            //保险退单 只能赋值给退票

            t.setInsurance(vasDto.getPrice() - vasDto.getDiscount());
            t.setInsuranceOrderNo(vasDto.getOrderNo());
            vasDto.getPassengers().remove(t.getPassengerName());

            logger.info("{}, {}", vasDto.getOrderNo(), vasDto.getPassengers().size());
            if (vasDto.getPassengers().isEmpty()) {
              vasDto.setRemoved(1);
            } else {
              vasDto.setCount(vasDto.getPassengers().size());
            }
          } else if (vasDto.getPrice() > 0 && t.getTicketType() == 0) {
            //出票
            t.setInsurance(vasDto.getPrice() - vasDto.getDiscount());
            t.setInsuranceOrderNo(vasDto.getOrderNo());
            vasDto.setRemoved(1);
          }
        }

      }
    }

    dto.setVasOrders(dto.getVasOrders().stream().filter(t -> t.getRemoved() != 1).collect(Collectors.toList()));

    //需要获取Y舱价格
    for (ReportTicketDto t : dto.getTickets()) {
      if (t.getTicketType() != 0) {
        continue;
      }

      for (ReportTicketDetailDto fltDto : t.getDetails()) {
        getFlightFullPrice(fltDto, t);
      }
    }
  }

  @Override
  @Secured({RoleInfo.ROLE_CASHIER, RoleInfo.ROLE_ADMIN})
  public JsonResult splitFlightBill(int enterpriseId, String username, int billId, int ticketId) {
    try {
      final String newBillNo = orderNoGenerator.generateOrderNo(enterpriseId, OrderNoInfo.BILL_FLIGHT);
      int retCode = billSplitDao.splitFlightBill(enterpriseId, username, billId, ticketId, newBillNo);
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  @Override
  @Secured({RoleInfo.ROLE_CASHIER, RoleInfo.ROLE_ADMIN})
  public JsonResult splitVasBill(int enterpriseId, String username, int billId, int orderId, int psgId) {
    try {
      final String newBillNo = orderNoGenerator.generateOrderNo(enterpriseId, OrderNoInfo.BILL_VAS);
      int retCode = billSplitDao.splitVasBill(enterpriseId, username, billId, orderId, psgId, newBillNo);
      return JsonResult.createJsonResult(retCode);
    } catch (RuntimeException ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  @Override
  public JsonResult removeAllBillsFromSettlement(int enterpriseId, String username, int settlementId) {
    try {
      int retCode = settlementDao.removeAllBills(enterpriseId, username, settlementId);
      return JsonResult.createJsonResult(retCode);
    } catch (Exception ex) {
      return JsonResult.createJsonResult(-100, ex.getMessage());
    }
  }

  private void getFlightFullPrice(ReportTicketDetailDto fltDto, ReportTicketDto ticketDto) {
    //TODO 暂时只考虑单程机票
    String dport = fltDto.getDport();
    String aport = fltDto.getAport();
    String carrier = fltDto.getCarrier();
    String subClass = fltDto.getSubclass();

    List<FlightPriceCache> priceCaches = flightPolicyDao.getSubclassPrice(dport, aport, carrier);
    if (priceCaches.isEmpty()) {
      String content = dport + "," + aport + "," + carrier + "," + subClass;
      redisDao.addToList(FlightPriceCache.FD_QUEUE, content);
    } else {
      //默认的舱位等级
      String cabinClass = "Y";
      //查找子舱位对应的物理舱位
      for (FlightPriceCache priceCache : priceCaches) {
        if (subClass.equalsIgnoreCase(priceCache.getSubclass())) {
          cabinClass = priceCache.getCabinClass();
          break;
        }
      }
      if (!StringUtils.isEmpty(cabinClass)) {
        int fullPrice = 0;
        for (FlightPriceCache priceCache : priceCaches) {
          if (cabinClass.equalsIgnoreCase(priceCache.getSubclass())) {
            fullPrice = priceCache.getPrice();
            break;
          }
        }
        if (fullPrice > 0) {
          //此处要根据子舱位的价格来获取对应全价舱位
          ticketDto.setFullPrice(fullPrice + ticketDto.getFullPrice());
        }
      }
    }
  }

  //3610 2021-09-30
}